/*! \file xmodem.c \brief XModem Transmit/Receive Implementation with CRC and 1K support. */
//*****************************************************************************
//
// File Name    : 'xmodem.c'
// Title        : XModem Transmit/Receive Implementation with CRC and 1K support
// Author       : Pascal Stang - Copyright (C) 2006
// Created      : 4/22/2006
// Revised      : 7/22/2006
// Version      : 0.1
// Target MCU   : AVR processors
// Editor Tabs  : 4
//
// This code is distributed under the GNU Public License
//      which can be found at http://www.gnu.org/licenses/gpl.txt
//
//*****************************************************************************

#include <string.h>
#include <stdint.h>

#include "xmodem.h"

//#define XMODEM_BUFFER_SIZE        128
#define XMODEM_BUFFER_SIZE      1024
#define XMODEM_RETRY_LIMIT 3

#define SOH         0x01
#define STX         0x02
#define EOT         0x04
#define ACK         0x06
#define NAK         0x15
#define CAN         0x18
#define CTRLZ       0x1A

// pointers to stream I/O functions
static void (*xmodemOut)(unsigned char c);
static int (*xmodemIn)(void);

// forward decls
uint16_t crc_xmodem_update(uint16_t crc, uint8_t data);
int xmodemInTime(unsigned short timeout);
void xmodemInFlush(void);
int xmodemCrcCheck(int crcflag, const unsigned char *buffer, int size);

void xmodemInit(void (*sendbyte_func)(unsigned char c), int (*getbyte_func)(void))
{
    // assign function pointers
    xmodemOut = sendbyte_func;
    xmodemIn = getbyte_func;
}

long xmodemReceive( int (*write)(unsigned char* buffer, int size) )
{
    // create xmodem buffer
    // 1024b for Xmodem 1K
    // 128 bytes for Xmodem std.
    // + 5b header/crc + NULL
    unsigned char xmbuf[XMODEM_BUFFER_SIZE+6];
    unsigned char seqnum=1;     // xmodem sequence number starts at 1
    unsigned short pktsize=128; // default packet size is 128 bytes
    unsigned char response='C'; // solicit a connection with CRC enabled
    char retry=XMODEM_RETRY_LIMIT;
    unsigned char crcflag=0;
    unsigned long totalbytes=0;
    int i,c;

    while(retry > 0)
    {
        // solicit a connection/packet
        xmodemOut(response);
        // wait for start of packet
        if( (c = xmodemInTime(XMODEM_TIMEOUT_DELAY)) >= 0)
        {
            switch(c)
            {
            case SOH:
                pktsize = 128;
                break;
            #if(XMODEM_BUFFER_SIZE>=1024)
            case STX:
                pktsize = 1024;
                break;
            #endif
            case EOT:
                xmodemInFlush();
                xmodemOut(ACK);
                // completed transmission normally
                return totalbytes;
            case CAN:
                if((c = xmodemInTime(XMODEM_TIMEOUT_DELAY)) == CAN)
                {
                    xmodemInFlush();
                    xmodemOut(ACK);
                    // transaction cancelled by remote node
                    return XMODEM_ERROR_REMOTECANCEL;
                }
            default:
                break;
            }
        }
        else
        {
            // timed out, try again
            // no need to flush because receive buffer is already empty
            retry--;
            //response = NAK;
            continue;
        }

        // check if CRC mode was accepted
        if(response == 'C') crcflag = 1;
        // got SOH/STX, add it to processing buffer
        xmbuf[0] = c;
        // try to get rest of packet
        for(i=0; i<(pktsize+crcflag+4-1); i++)
        {
            if((c = xmodemInTime(XMODEM_TIMEOUT_DELAY)) >= 0)
            {
                xmbuf[1+i] = c;
            }
            else
            {
                // timed out, try again
                retry--;
                xmodemInFlush();
                response = NAK;
                break;
            }
        }
        // packet was too small, retry
        if(i<(pktsize+crcflag+4-1))
            continue;

        // got whole packet
        // check validity of packet
        if( (xmbuf[1] == (unsigned char)(~xmbuf[2])) &&     // sequence number was transmitted w/o error
            xmodemCrcCheck(crcflag, &xmbuf[3], pktsize) )   // packet is not corrupt
        {
            // is this the packet we were waiting for?
            if(xmbuf[1] == seqnum)
            {
                // write/deliver data
                write(&xmbuf[3], pktsize);
                //spiflashWrite(flashaddr, pktsize, &xmbuf[3]);
                totalbytes += pktsize;
                // next sequence number
                seqnum++;
                // reset retries
                retry = XMODEM_RETRY_LIMIT;
                // reply with ACK
                response = ACK;
                continue;
            }
            else if(xmbuf[1] == (unsigned char)(seqnum-1))
            {
                // this is a retransmission of the last packet
                // ACK and move on
                response = ACK;
                continue;
            }
            else
            {
                // we are completely out of sync
                // cancel transmission
                xmodemInFlush();
                xmodemOut(CAN);
                xmodemOut(CAN);
                xmodemOut(CAN);
                return XMODEM_ERROR_OUTOFSYNC;
            }
        }
        else
        {
            // packet was corrupt
            // NAK it and try again
            retry--;
            xmodemInFlush();
            response = NAK;
            continue;
        }
    }

    // exceeded retry count
    xmodemInFlush();
    xmodemOut(CAN);
    xmodemOut(CAN);
    xmodemOut(CAN);
    return XMODEM_ERROR_RETRYEXCEED;
}


long xmodemTransmit( int (*read)(unsigned char* buffer, int size) )
{
    // still to be written
    return 0;
}

uint16_t crc_xmodem_update(uint16_t crc, uint8_t data)
{
    int i;

    crc = crc ^ ((uint16_t)data << 8);
    for (i=0; i<8; i++)
    {
        if(crc & 0x8000)
            crc = (crc << 1) ^ 0x1021;
        else
            crc <<= 1;
    }

    return crc;
}

int xmodemCrcCheck(int crcflag, const unsigned char *buffer, int size)
{
    // crcflag=0 - do regular checksum
    // crcflag=1 - do CRC checksum

    if(crcflag)
    {
        unsigned short crc=0;
        unsigned short pktcrc = (buffer[size]<<8)+buffer[size+1];
        // do CRC checksum
        while(size--)
            crc = crc_xmodem_update(crc, *buffer++);
        // check checksum against packet
        if(crc == pktcrc)
            return 1;
    }
    else
    {
        int i;
        unsigned char cksum = 0;
        // do regular checksum
        for(i=0; i<size; ++i)
        {
            cksum += buffer[i];
        }
        // check checksum against packet
        if(cksum == buffer[size])
            return 1;
    }

    return 0;
}


int xmodemInTime(unsigned short timeout)
{
    int c=-1;

    //while( (timeout--) && ((c=xmodemIn()) < 0) )
	while((c = xmodemIn()) < 0)
      {
        // fixme: we do not have this: timerPause(1);
      }

    return c;
}

void xmodemInFlush(void)
{
    while(xmodemInTime(XMODEM_TIMEOUT_DELAY) >= 0);
}
